package com.ficus.road.maintain.authority.service;

import com.ficus.road.maintain.authority.dao.RoleDao;
import com.ficus.road.maintain.authority.dao.UserDao;
import com.ficus.road.maintain.authority.dto.RoleDTO;
import com.ficus.road.maintain.authority.dto.UserDTO;
import com.ficus.road.maintain.authority.vo.LoginErrorResponse;
import com.ficus.road.maintain.authority.vo.LoginResponse;
import com.ficus.road.maintain.authority.vo.UserUpdateVO;
import com.ficus.road.maintain.authority.vo.UserVO;
import com.ficus.road.maintain.core.exception.BadRequestException;
import com.ficus.road.maintain.core.exception.CommonException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.net.URLDecoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author xiawen yang
 * @date 2021/8/25 下午7:23
 */
@Service
@Slf4j
public class UserService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private RoleDao roleDao;

    @Autowired
    private RoleService roleService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;


    public List<UserVO> queryUsersByQuery(Query query) {
        query.addCriteria(Criteria.where("enable").is(Boolean.TRUE));
        List<UserDTO> users = userDao.findByQuery(query);
        ArrayList<UserVO> userVOS = new ArrayList<>();
        users.stream().forEach(
                user -> {
                    List<String> roleNames = new ArrayList<>();
                    user.getRoleIdList().stream().forEach(
                            roleId -> {
                                RoleDTO role = roleDao.findOne(new Query(Criteria.where("id").is(roleId)));
                                roleNames.add(role.getRoleName());
                            }
                    );
                    UserVO userVO = UserVO.builder().userId(user.getId()).createTime(user.getCreateTime()).account(user.getAccount()).password(user.getPassword())
                            .userName(user.getUsername()).carIdList(user.getCarIdList()).roles(roleNames).build();
                    userVOS.add(userVO);
                }
        );
        return userVOS;
    }

    public String createUser(UserVO user) {
        if (userDao.count(new Query(Criteria.where("account").is(user.getAccount()))) != 0) {
            log.error("createUser fail, account is exist");
            throw new BadRequestException("账号已存在！");
        }
        Set<String> roleIds = new HashSet<>();
        user.getRoles().stream().forEach(
                roleName -> {
                    RoleDTO role = roleDao.findOne(new Query(Criteria.where("role_name").is(roleName)));
                    if (Objects.isNull(role)) {
                        throw new CommonException("role " + roleName + "is not exist", 400);
                    }
                    roleIds.add(role.getId());
                }
        );
        UserDTO userDTO = UserDTO.builder().account(user.getAccount()).username(user.getUserName()).password(bCryptPasswordEncoder.encode(user.getPassword()))
                .roleIdList(roleIds).carIdList(user.getCarIdList()).createTime(new Date().getTime() / 1000).enable(Boolean.TRUE).build();
        UserDTO res = userDao.saveOrUpdate(userDTO);
        return res.getId();
    }

    public String updateUser(String userId, UserUpdateVO user) {
        UserDTO fetch = userDao.fetch(userId);
        if (Objects.isNull(fetch)) {
            log.error("updateUser fail, userId:{} is not exist", userId);
            throw new CommonException("updateUser fail, userId" + userId + " is not exist", 400);
        }
        Set<String> roleIds = new HashSet<>();
        user.getRoles().stream().forEach(
                roleName -> {
                    RoleDTO role = roleDao.findOne(new Query(Criteria.where("role_name").is(roleName)));
                    if (Objects.isNull(role)) {
                        throw new CommonException("role " + roleName + "is not exist", 400);
                    }
                    roleIds.add(role.getId());
                }
        );
        UserDTO userDTO = new UserDTO();
        String password = user.getPassword();
        if (StringUtils.isNotBlank(password)) {
            userDTO.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        } else {
            userDTO.setPassword(fetch.getPassword());
        }
        userDTO.setUsername(user.getUserName());
        userDTO.setRoleIdList(roleIds);
        userDTO.setCarIdList(user.getCarIdList());
        userDTO.setUpdateTime(new Date().getTime() / 1000);
        userDTO.setEnable(Boolean.TRUE);
        //不更新账号、创建时间
        userDTO.setId(fetch.getId());
        userDTO.setCreateTime(fetch.getCreateTime());
        userDTO.setAccount(fetch.getAccount());
        userDao.saveOrUpdate(userDTO);
        return userId;
    }

    public String deleteUser(String userId) {
        if (getCurrentUser().getId().equals(userId)) {
            log.error("deleteUser fail! you can't delete current user!");
            throw new BadRequestException("deleteUser fail! you can't delete current user!");
        }
        UserDTO fetch = userDao.fetch(userId);
        fetch.setUpdateTime(new Date().getTime() / 1000);
        fetch.setEnable(Boolean.FALSE);
        userDao.saveOrUpdate(fetch);
        return userId;
    }

    public LoginResponse queryLoginInfoByUsername(String username) {
        UserDTO userDTO = queryUserByUsername(username);
        List<RoleDTO> roleDTOS = roleService.queryRolesById(userDTO.getRoleIdList());

        LoginResponse loginResponse = new LoginResponse();
        loginResponse.setUserId(userDTO.getId());
        loginResponse.setUserName(userDTO.getUsername());
        loginResponse.setFrontConfigs(roleDTOS.stream().flatMap(role -> role.getFrontConfigs().stream()).collect(Collectors.toSet()));
        loginResponse.setRoleIdList(userDTO.getRoleIdList());
        loginResponse.setCarIdList(userDTO.getCarIdList());
        loginResponse.setRoleNameList(roleDTOS.stream().map(RoleDTO::getRoleName).collect(Collectors.toSet()));
        //如该用户无密码修改时间，则改为当前时间
        long currentTimestampSeconds = System.currentTimeMillis() / 1000;
        if (userDTO.getPassWordUpdateTime() == null){
            userDTO.setPassWordUpdateTime(currentTimestampSeconds);
        }
        // 如果密码输入错误次数小于0,则代表账户已锁定，无法登陆
        if (userDTO.getLockTime()!=null&&userDTO.getLockTime()!=0){
            Long lockTime = userDTO.getLockTime();
            long newTimestampSeconds = System.currentTimeMillis() / 1000;
            if (newTimestampSeconds - lockTime>60){
                // 登陆成功后，将错误次数改成5次
                userDTO.setErrorsNumber(5);
                userDTO.setLockTime(0L);
                userDao.saveOrUpdate(userDTO);
                loginResponse.setErrorsNumber(userDTO.getErrorsNumber());
            }else {
                long l = 60-(newTimestampSeconds - lockTime);
                loginResponse.setLockTime(l);
                loginResponse.setErrorsNumber(-1);
            }
        }else {
            loginResponse.setErrorsNumber(userDTO.getErrorsNumber());
        }
        //判断账户密码是否过期
        if ((currentTimestampSeconds - userDTO.getPassWordUpdateTime())>7776000){
            loginResponse.setToBeExpired(true);
        }
        return loginResponse;
    }

    /**
     * 车载登陆错误处理
     * @param data
     * @return
     */
    public LoginErrorResponse queryLoginError(Map<String, String[]> data) {
        LoginErrorResponse loginErrorResponse = new LoginErrorResponse();
        if (data!=null){
            // 创建对象并设置属性
            String username = data.get("username")[0];
            // 暂时保留，可能会有用
//        String password = paramMap.get("password");
            UserDTO userDTO = queryUserByUsername(username);
            int errorsNumber = userDTO.getErrorsNumber();
            long newTimestampSeconds = System.currentTimeMillis() / 1000;
            if(errorsNumber>1){
                // 登陆失败后，将错误次数减少1次
                userDTO.setErrorsNumber(errorsNumber-1);
                userDao.saveOrUpdate(userDTO);
                loginErrorResponse.setErrorsNumber(userDTO.getErrorsNumber());
            }else if (errorsNumber==0){
                // 登陆失败后，将错误次数减少1次
                userDTO.setErrorsNumber(4);
                userDao.saveOrUpdate(userDTO);
                loginErrorResponse.setErrorsNumber(userDTO.getErrorsNumber());
            }else {
                // 已锁定状态
                if (userDTO.getLockTime()!=null&&userDTO.getLockTime()!=0){
                    Long lockTime = userDTO.getLockTime();
                    // 如果已超过了60秒，解除锁定
                    if (newTimestampSeconds - lockTime>60){
                        userDTO.setErrorsNumber(4);
                        userDTO.setLockTime(0L);
                        userDao.saveOrUpdate(userDTO);
                        loginErrorResponse.setErrorsNumber(userDTO.getErrorsNumber());
                    }else {
                        // 登陆失败超过5次后，锁定该用户
                        long l = 60-(newTimestampSeconds - lockTime);
                        loginErrorResponse.setLockTime(l);
                        loginErrorResponse.setErrorsNumber(-1);
                        loginErrorResponse.setMessage("密码多次输入错误，账户已锁定");
                    }
                    //未锁定状态，加锁
                }else {
                    // 登陆失败超过5次后，锁定该用户
                    loginErrorResponse.setErrorsNumber(-1);
                    userDTO.setLockTime(newTimestampSeconds);
                    userDao.saveOrUpdate(userDTO);
                    loginErrorResponse.setLockTime(60L);
                    loginErrorResponse.setErrorsNumber(-1);
                    loginErrorResponse.setMessage("账户已锁定");
                }

            }
        }else {
            loginErrorResponse.setErrorsNumber(-1);
            loginErrorResponse.setMessage("未输入账号");
        }
        return loginErrorResponse;
    }

    public UserDTO queryUserByUsername(String username) {
        Query query = new Query(Criteria.where("account").is(username));
        UserDTO user = userDao.findOne(query);
        if (Objects.isNull(user) || !user.getEnable()) {
            log.error("user not found! username: {}", username);
            throw new UsernameNotFoundException("当前用户不存在！");
        }
        return user;
    }

    //方法内用account去查询，而不是用username，因为userDetailsServiceImpl就是用account去封装User的
    public UserDTO getCurrentUser() {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof User) {
            String username = ((User) (principal)).getUsername();
            return this.queryUserByUsername(username);
        }
        log.error("Get current user fail!");
        throw new CommonException("Get current user fail!", 500);
    }

    public UserDTO queryUserById(String userId) {
        UserDTO userDTO = userDao.fetch(userId);
        if (Objects.isNull(userDTO)) {
            throw new BadRequestException("该用户不存在！");
        }
        return userDTO;
    }

    public String updatePassword(String userId, UserUpdateVO userUpdateVO) {
        //只能修改当前账户的密码
        UserDTO currentUser = getCurrentUser();
        currentUser.setPassword(bCryptPasswordEncoder.encode(userUpdateVO.getPassword()));
        // 修改密码后，将修改密码时间改成当前时间
        long currentTimestampSeconds = System.currentTimeMillis() / 1000;
        currentUser.setPassWordUpdateTime(currentTimestampSeconds);
        userDao.saveOrUpdate(currentUser);
        return userId;
    }

    public UserDTO verifierPassword(String password, String username) {
        UserDTO userDTO = queryUserByUsername(username);
        if (!bCryptPasswordEncoder.matches(password, userDTO.getPassword())) {
            throw new BadRequestException("用户名密码错误,请确认！");
        }
        return userDTO;
    }
}
